﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using HtmlAgilityPack;
using System.IO;
using LingDong.ConfigProvider;

namespace LingDong.HtmlParser
{
    public class HtmlContent
    {
        #region Html Content

        public static string GetHtmlContentByFilePath(string path)
        {
            HtmlDocument doc = new HtmlDocument();
            doc.Load(path);
            return GetHtmlContent(doc);
        }

        public static string GetHtmlContent(string html)
        {
            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(html);
            return GetHtmlContent(doc);
        }

        public static string GetHtmlContent(HtmlDocument doc)
        {
            StringWriter sw = new StringWriter();
            ConvertTo(doc.DocumentNode, sw, true);
            sw.Flush();
            return sw.ToString();
        }

        public static string GetHtmlAbstract(string html)
        {
            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(html);
            return GetHtmlAbstract(doc);
        }

        public static string GetHtmlAbstract(HtmlDocument doc)
        {
            StringWriter sw = new StringWriter();
            ConvertTo(doc.DocumentNode, sw, false);
            sw.Flush();
            return sw.ToString();
        }

        #endregion


        #region Public Methods

        /// <summary>
        /// Get html declared encoding, charset="xxxxx"
        /// </summary>
        /// <param name="html"></param>
        /// <returns></returns>
        public static Encoding GetDeclaredEncoding(string html)
        {
            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(html);
            return (doc.DeclaredEncoding == null) ? htmlDefaultEncoding : doc.DeclaredEncoding;
        }

        /// <summary>
        /// Parse the html, get title and content
        /// </summary>
        /// <param name="html">input html</param>
        /// <param name="title">title</param>
        /// <returns>content</returns>
        public static string ParseHtml(string html, out string title)
        {
            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(html);
            
            HtmlNode titleNode = doc.DocumentNode.SelectSingleNode("//title");
            title = (titleNode == null) ? String.Empty : titleNode.InnerText;

            return GetHtmlContent(doc);
        }

        #endregion


        #region Private Methods

        private static void ConvertTo(HtmlNode node, TextWriter outText, bool fullText)
        {
            string html;
            switch (node.NodeType)
            {
                case HtmlNodeType.Comment:
                    // don't output comments
                    break;

                case HtmlNodeType.Document:
                    ConvertContentTo(node, outText, fullText);
                    break;

                case HtmlNodeType.Text:
                    // script and style must not be output
                    string parentName = node.ParentNode.Name;
                    if ((parentName == "script") || (parentName == "style"))
                        break;

                    // get text
                    html = ((HtmlTextNode)node).Text;

                    // is it in fact a special closing node output as text?
                    if (HtmlNode.IsOverlappedClosingElement(html))
                        break;

                    // check the text is meaningful and not a bunch of whitespaces
                    if (html.Trim().Length > 0)
                    {
                        if (fullText)
                            outText.Write(HtmlEntity.DeEntitize(html));
                        else
                            outText.Write(GetAbstractText(html));
                    }
                    break;

                case HtmlNodeType.Element:
                    switch (node.Name)
                    {
                        case "p":
                            // treat paragraphs as crlf
                            if (fullText)
                                outText.Write("\r\n");
                            break;
                    }

                    if (node.HasChildNodes)
                    {
                        ConvertContentTo(node, outText, fullText);
                    }
                    break;
            }
        }

        private static void ConvertContentTo(HtmlNode node, TextWriter outText, bool fullText)
        {
            foreach (HtmlNode subnode in node.ChildNodes)
            {
                ConvertTo(subnode, outText, fullText);
            }
        }

        private static string GetAbstractText(string html)
        {
            string content = HtmlEntity.DeEntitize(html);
            if (content.Length < ParagraphMinLen)
                return "";

            // get first sentence of the paragraph
            string[] sentenceList = content.Split(ConfigProvider.SentenceSplitter.Get(),
                                                  StringSplitOptions.RemoveEmptyEntries).
                                                  Select(sen => sen.Trim()).ToArray();
            StringBuilder result = new StringBuilder();
            int cuurentLen = 0;
            foreach (string sen in sentenceList)
            {
                result.Append(sen);
                cuurentLen += sen.Length;
                if (cuurentLen >= AbstractSencentMinLen)
                    break;
            }
            result.Append("\r\n");
            return result.ToString();
        }

        #endregion

        private static Encoding htmlDefaultEncoding = Encoding.GetEncoding(ConfigDataProvider.Default.HTMLDefaultEncoding);
        private static int ParagraphMinLen = 35;
        private static int AbstractSencentMinLen = 12;
    }
}
